
package com.tonyodev.fetch2fileserver;

import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.utils.zson.ZSONObject;

import com.tonyodev.fetch2core.Extras;
import com.tonyodev.fetch2core.FetchCoreUtils;
import com.tonyodev.fetch2core.FetchLogger;
import com.tonyodev.fetch2core.FileResource;
import com.tonyodev.fetch2core.Func;
import com.tonyodev.fetch2core.Func2;
import com.tonyodev.fetch2core.InputResourceWrapper;
import com.tonyodev.fetch2core.InterruptMonitor;
import com.tonyodev.fetch2core.server.FileRequest;
import com.tonyodev.fetch2core.server.FileResourceTransporterWriter;
import com.tonyodev.fetch2fileserver.database.FetchFileResourceInfoDatabase;
import com.tonyodev.fetch2fileserver.database.FileResourceExtensionsKt;
import com.tonyodev.fetch2fileserver.database.FileResourceInfo;
import com.tonyodev.fetch2fileserver.provider.FetchFileResourceProvider;
import com.tonyodev.fetch2fileserver.provider.FileResourceProvider;
import com.tonyodev.fetch2fileserver.provider.FileResourceProviderDelegate;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

public class FetchFileServerImpl implements FetchFileServer {
    private Context context;
    private ServerSocket serverSocket;
    private boolean clearFileResourcesDatabaseOnShutdown;
    private FetchLogger logger;
    private String databaseName;
    private FetchFileServerAuthenticator fetchFileServerAuthenticator;
    private FetchFileServerDelegate fetchFileServerDelegate;
    private FetchTransferListener fetchTransferListener;
    private long progressReportingInMillis;
    private long persistentTimeoutInMillis;
    private FileResolver fileResolver;
    private Object lock = new Object();
    private String uuid = UUID.randomUUID().toString();
    private Map<String, FileResourceProvider> fileResourceProviderMap = Collections.synchronizedMap(new HashMap<String, FileResourceProvider>());
    private boolean isTerminated = false;
    private boolean isForcedTerminated = false;
    private boolean isStarted = false;
    private final FetchFileResourceInfoDatabase fileResourceServerDatabase;
    private EventHandler ioHandler = new EventHandler(EventRunner.create());
    private EventHandler mainHandler = new EventHandler(EventRunner.getMainEventRunner());
    private InputResourceWrapper inputResourceWrapper;


    public FetchFileServerImpl(Context context, ServerSocket serverSocket, boolean clearFileResourcesDatabaseOnShutdown,
                               FetchLogger logger, String databaseName,
                               FetchFileServerAuthenticator fetchFileServerAuthenticator,
                               FetchFileServerDelegate fetchFileServerDelegate, FetchTransferListener fetchTransferListener, long progressReportingInMillis, long persistentTimeoutInMillis, FileResolver fileResolver) {

        this.context = context;
        this.serverSocket = serverSocket;
        this.clearFileResourcesDatabaseOnShutdown = clearFileResourcesDatabaseOnShutdown;
        this.logger = logger;
        this.databaseName = databaseName;
        this.fetchFileServerAuthenticator = fetchFileServerAuthenticator;
        this.fetchFileServerDelegate = fetchFileServerDelegate;
        this.fetchTransferListener = fetchTransferListener;
        this.progressReportingInMillis = progressReportingInMillis;
        this.persistentTimeoutInMillis = persistentTimeoutInMillis;
        this.fileResolver = fileResolver;
        fileResourceServerDatabase = new FetchFileResourceInfoDatabase(context, databaseName);
    }

    public Context getContext() {
        return context;
    }

    public ServerSocket getServerSocket() {
        return serverSocket;
    }

    public boolean isClearFileResourcesDatabaseOnShutdown() {
        return clearFileResourcesDatabaseOnShutdown;
    }

    public FetchLogger getLogger() {
        return logger;
    }

    public String getDatabaseName() {
        return databaseName;
    }

    public FetchFileServerAuthenticator getFetchFileServerAuthenticator() {
        return fetchFileServerAuthenticator;
    }

    public FetchFileServerDelegate getFetchFileServerDelegate() {
        return fetchFileServerDelegate;
    }

    public FetchTransferListener getFetchTransferListener() {
        return fetchTransferListener;
    }

    public long getProgressReportingInMillis() {
        return progressReportingInMillis;
    }

    public long getPersistentTimeoutInMillis() {
        return persistentTimeoutInMillis;
    }

    public FileResolver getFileResolver() {
        return fileResolver;
    }

    @Override
    public String getId() {
        return this.uuid;
    }

    @Override
    public int getPort() {
        return !isTerminated ? serverSocket.getLocalPort() : 0;
    }

    @Override
    public String getAddress() {
        if (!isTerminated) {
            return serverSocket.getInetAddress().getHostAddress();
        }
        return "00:00:00:00";
    }

    @Override
    public boolean isShutDown() {
        return isTerminated;
    }

    @Override
    public void start() {
        synchronized (lock) {
            try {
                throwIfTerminated();
            } catch (Exception e) {
                System.out.println("报错信息" + e.getMessage());
            }
            if (!isStarted && !isTerminated) {
                isStarted = true;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        while (!isTerminated) {
                            try {
                                Socket client = serverSocket.accept();
                                if (!isTerminated) {
                                    processClient(client);
                                } else {
                                    client.close();
                                }
                            } catch (Exception e) {

                            }
                        }
                        cleanUpServer();
                    }
                }).start();
            }
        }
    }

    private void processClient(Socket clientSocket) {
        FileResourceProviderDelegate fileResourceProviderDelegate = new FileResourceProviderDelegateObject();
        if (!isTerminated) {
            FetchFileResourceProvider fileResourceProvider = new FetchFileResourceProvider(clientSocket, fileResourceProviderDelegate, logger,
                ioHandler, progressReportingInMillis, persistentTimeoutInMillis, fileResolver);
            try {
                fileResourceProviderMap.put(fileResourceProvider.getId(), fileResourceProvider);
                fileResourceProvider.execute();
            } catch (Exception e) {
                System.out.println("FetchFileServer" + "- " + e.getMessage());
                fileResourceProviderDelegate.onFinished(fileResourceProvider.getId());
            }
        }
    }

    private class FileResourceProviderDelegateObject implements FileResourceProviderDelegate {

        @Override
        public void onFinished(String providerId) {
            try {
                fileResourceProviderMap.remove(providerId);
            } catch (Exception e) {
                System.out.println("FetchFileServer" + "-" + e.getMessage());
            }
            if (fileResourceProviderMap.isEmpty() && isTerminated) {
                cleanUpServer();
            }
        }

        @Override
        public FileResource getFileResource(String fileResourceIdentifier) {
            try {
                long id = Long.parseLong(fileResourceIdentifier);
                if (id == FileRequest.CATALOG_ID) {
                    return getCatalogResourceFile();
                } else {
                    return FileResourceExtensionsKt.toFileResource(fileResourceServerDatabase.get(id));
                }
            } catch (Exception e) {
                if (fileResourceIdentifier.equals(FileRequest.CATALOG_NAME)) {
                    return getCatalogResourceFile();
                } else {
                    FileResourceInfo fileResourceInfo = fileResourceServerDatabase.get(fileResourceIdentifier);
                    if (fileResourceInfo != null) {
                        return FileResourceExtensionsKt.toFileResource(fileResourceInfo);
                    } else {
                        return null;
                    }
                }
            }
        }

        private FileResource getCatalogResourceFile() {
            String catalog = fileResourceServerDatabase.getRequestedCatalog();
            FileResourceInfo catalogFileResourceInfo = new FileResourceInfo();
            catalogFileResourceInfo.setId(FileRequest.CATALOG_ID);
            Map<String, Object> catalogMap = new LinkedHashMap<String, Object>();
            catalogMap.put("data", catalog);
            catalogFileResourceInfo.setExtras((new ZSONObject(catalogMap)).toString());
            catalogFileResourceInfo.setName(FileRequest.CATALOG_NAME);
            catalogFileResourceInfo.setFile(FileRequest.CATALOG_FILE);
            return FileResourceExtensionsKt.toFileResource(catalogFileResourceInfo);
        }

        @Override
        public boolean acceptAuthorization(String sessionId, String authorization, FileRequest fileRequest) {
            if (fetchFileServerAuthenticator != null) {
                return fetchFileServerAuthenticator.accept(sessionId, authorization, fileRequest);
            }
            return true;
        }

        @Override
        public void onClientConnected(String sessionId, FileRequest fileRequest) {

            mainHandler.postTask(new Runnable() {
                @Override
                public void run() {
                    if (fetchFileServerDelegate != null) {
                        fetchFileServerDelegate.onClientConnected(sessionId, fileRequest);
                    }
                }
            });

        }

        @Override
        public void onClientDidProvideExtras(String sessionId, Extras extras, FileRequest fileRequest) {
            if (fetchFileServerDelegate != null) {
                fetchFileServerDelegate.onClientDidProvideExtras(sessionId, extras, fileRequest);
            }
        }


        @Override
        public void onClientDisconnected(String sessionId, FileRequest fileRequest) {
            mainHandler.postTask(new Runnable() {
                @Override
                public void run() {
                    if (fetchFileServerDelegate != null) {
                        fetchFileServerDelegate.onClientDisconnected(sessionId, fileRequest);
                    }
                }
            });
        }

        @Override
        public String getCatalog(int page, int size) {
            return fileResourceServerDatabase.getRequestedCatalog(page, size);
        }

        @Override
        public InputResourceWrapper getFileInputResourceWrapper(String sessionId, FileRequest fileRequest, FileResource fileResource, long fileOffset) {
            if (fetchFileServerDelegate != null) {
                return fetchFileServerDelegate.getFileInputResourceWrapper(sessionId, fileRequest, fileResource, fileOffset);
            }
            return inputResourceWrapper;
        }

        @Override
        public void onStarted(String sessionId, FileRequest fileRequest, FileResource fileResource) {

            mainHandler.postTask(new Runnable() {
                @Override
                public void run() {
                    if (fetchTransferListener != null) {
                        fetchTransferListener.onStarted(sessionId, fileRequest, fileResource);
                    }
                }
            });

        }

        @Override
        public void onProgress(String sessionId, FileRequest fileRequest, FileResource fileResource, int progress) {
            mainHandler.postTask(new Runnable() {
                @Override
                public void run() {
                    if (fetchTransferListener != null) {
                        fetchTransferListener.onProgress(sessionId, fileRequest, fileResource, progress);
                    }
                }
            });

        }

        @Override
        public void onComplete(String sessionId, FileRequest fileRequest, FileResource fileResource) {
            mainHandler.postTask(new Runnable() {
                @Override
                public void run() {
                    if (fetchTransferListener != null) {
                        fetchTransferListener.onComplete(sessionId, fileRequest, fileResource);
                    }
                }
            });
        }

        @Override
        public void onError(String sessionId, FileRequest fileRequest, FileResource fileResource, Throwable throwable) {
            mainHandler.postTask(new Runnable() {
                @Override
                public void run() {
                    if (fetchTransferListener != null) {
                        fetchTransferListener.onError(sessionId, fileRequest, fileResource, throwable);
                    }
                }
            });
        }


        @Override
        public void onCustomRequest(String sessionId, FileRequest fileRequest, FileResourceTransporterWriter fileResourceTransporterWriter, InterruptMonitor interruptMonitor) {
            if (fetchFileServerDelegate != null) {
                fetchFileServerDelegate.onCustomRequest(sessionId, fileRequest, fileResourceTransporterWriter, interruptMonitor);
            }
        }

    }

    private void cleanUpServer() {
        try {
            if (!serverSocket.isClosed()) {
                serverSocket.close();
            }
        } catch (Exception e) {
            System.out.println("报错信息" + e.getMessage());
        }
        try {
            fileResourceProviderMap.clear();
        } catch (Exception e) {
            System.out.println("报错信息2" + e.getMessage());
        }
        try {
            if (clearFileResourcesDatabaseOnShutdown) {

                ioHandler.postTask(new Runnable() {
                    @Override
                    public void run() {
                        fileResourceServerDatabase.deleteAll();
                        try {
                            fileResourceServerDatabase.close();
                        } catch (Exception e) {
                            System.out.println("报错信息3" + e.getMessage());
                        }
                        try {
                            ioHandler.removeTask(null);
                        } catch (Exception e) {
                            System.out.println("报错信息4" + e.getMessage());
                        }
                    }
                });
            } else {
                try {
                    fileResourceServerDatabase.close();
                } catch (Exception e) {
                    System.out.println("报错信息5" + e.getMessage());
                }
                try {
                    ioHandler.removeTask(null);
                } catch (Exception e) {

                }
            }
        } catch (Exception e) {
            System.out.println("报错信息6" + e.getMessage());
        }
        isStarted = false;
    }

    @Override
    public void shutDown(boolean forced) {
        synchronized (lock) {
            if (!isTerminated) {
                isTerminated = true;
                isForcedTerminated = forced;
                interruptAllProviders();
                if (isForcedTerminated) {
                    forceShutdown();
                }
            }
        }
    }

    private void forceShutdown() {
        cleanUpServer();
    }

    private void interruptAllProviders() {
        try {
//            for(String key:fileResourceProviderMap.keySet()){
//               FileResourceProvider fileResourceProvider=fileResourceProviderMap.get(key);
//                fileResourceProvider.interrupt();
//            }
            Iterator<Map.Entry<String, FileResourceProvider>> iterator = fileResourceProviderMap.entrySet().iterator();
            while (iterator.hasNext()) {
                iterator.next().getValue().interrupt();
            }
            if (fileResourceProviderMap.isEmpty()) {
                cleanUpServer();
            }
        } catch (Exception e) {
            System.out.println("报错信息7" + e.getMessage());
        }
    }

    @Override
    public void addFileResource(FileResource fileResource) {
        synchronized (lock) {
            try {
                throwIfTerminated();
                ioHandler.postTask(new Runnable() {
                    @Override
                    public void run() {
                        throwIfAddingReservedCatalogInfo(fileResource);
                        if (fileResource.getMd5().isEmpty()) {
                            fileResource.setMd5(getMd5CheckSumForFileResource(fileResource));
                        }
                        fileResourceServerDatabase.insert(FileResourceExtensionsKt.toFileResourceInfo(fileResource));
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

    @Override
    public void addFileResources(Collection<FileResource> fileResources) {
        List<FileResourceInfo> fileResourceInfos = new ArrayList<>();
        synchronized (lock) {
            try {
                throwIfTerminated();
                ioHandler.postTask(new Runnable() {
                    @Override
                    public void run() {
                        for (FileResource fileResource : fileResources) {
                            throwIfAddingReservedCatalogInfo(fileResource);
                            if (fileResource.getMd5().isEmpty()) {
                                fileResource.setMd5(getMd5CheckSumForFileResource(fileResource));
                            }
                            fileResourceInfos.add(FileResourceExtensionsKt.toFileResourceInfo(fileResource));
                        }
                        fileResourceServerDatabase.insert(fileResourceInfos);
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void removeFileResource(FileResource fileResource) {
        synchronized (lock) {
            try {
                throwIfTerminated();
            } catch (Exception e) {
                e.printStackTrace();
            }
            ioHandler.postTask(new Runnable() {
                @Override
                public void run() {
                    for (String key : fileResourceProviderMap.keySet()) {
                        FileResourceProvider provider = fileResourceProviderMap.get(key);
                        if (provider.isServingFileResource(fileResource)) {
                            provider.interrupt();
                            break;
                        }
                    }
                }
            });
        }

    }


    @Override
    public void removeAllFileResources() {
        synchronized (lock) {
            try {
                throwIfTerminated();
            } catch (Exception e) {
                e.printStackTrace();
            }
            ioHandler.postTask(new Runnable() {
                @Override
                public void run() {
                    fileResourceServerDatabase.deleteAll();
                    interruptAllProviders();
                }
            });
        }
    }

    @Override
    public void removeFileResources(Collection<FileResource> fileResources) {
        List<FileResourceInfo> fileResourceInfos = new ArrayList<>();
        synchronized (lock) {
            try {
                throwIfTerminated();
            } catch (Exception e) {
                e.printStackTrace();
            }
            ioHandler.postTask(new Runnable() {
                @Override
                public void run() {
                    for (FileResource fileResource : fileResources) {
                        fileResourceInfos.add(FileResourceExtensionsKt.toFileResourceInfo(fileResource));
                        for (String key : fileResourceProviderMap.keySet()) {
                            FileResourceProvider provider = fileResourceProviderMap.get(key);
                            if (provider.isServingFileResource(fileResource)) {
                                provider.interrupt();
                                break;
                            }
                        }
                    }
                    fileResourceServerDatabase.delete(fileResourceInfos);
                }
            });
        }
    }

    @Override
    public void getFileResources(Func<List<FileResource>> func) {
        List<FileResource> infos = new ArrayList<>();
        synchronized (lock) {
            try {
                throwIfTerminated();
            } catch (Exception e) {
                e.printStackTrace();
            }
            ioHandler.postTask(new Runnable() {
                @Override
                public void run() {
                    List<FileResourceInfo> filesResources = fileResourceServerDatabase.get();
                    mainHandler.postTask(new Runnable() {
                        @Override
                        public void run() {
                            for (FileResourceInfo fileResourceInfo : filesResources) {
                                infos.add(FileResourceExtensionsKt.toFileResource(fileResourceInfo));
                            }
                            func.call(infos);
                        }
                    });
                }
            });
        }
    }

    @Override
    public void containsFileResource(long fileResourceId, Func<Boolean> func) {
        synchronized (lock) {
            try {
                throwIfTerminated();
            } catch (Exception e) {
                e.printStackTrace();
            }
            ioHandler.postTask(new Runnable() {
                @Override
                public void run() {
                    List<FileResourceInfo> filesResources = fileResourceServerDatabase.get();
                    mainHandler.postTask(new Runnable() {
                        @Override
                        public void run() {
                            if (filesResources != null) {
                                func.call(true);
                            } else {
                                func.call(false);
                            }
                        }
                    });
                }
            });
        }

    }

    @Override
    public void getFileResource(long fileResourceId, Func2<FileResource> func2) {
        synchronized (lock) {
            try {
                throwIfTerminated();
            } catch (Exception e) {
                e.printStackTrace();
            }
            ioHandler.postTask(new Runnable() {
                @Override
                public void run() {
                    FileResourceInfo fileResource = fileResourceServerDatabase.get(fileResourceId);
                    mainHandler.postTask(new Runnable() {
                        @Override
                        public void run() {
                            func2.call(FileResourceExtensionsKt.toFileResource(fileResource));
                        }
                    });
                }
            });
        }
    }

    @Override
    public void getCatalog(Func<String> func) {
        synchronized (lock) {
            try {
                throwIfTerminated();
            } catch (Exception e) {
                e.printStackTrace();
            }
            ioHandler.postTask(new Runnable() {
                @Override
                public void run() {
                    String catalog = fileResourceServerDatabase.getRequestedCatalog();
                    mainHandler.postTask(new Runnable() {
                        @Override
                        public void run() {
                            func.call(catalog);
                        }
                    });
                }
            });
        }
    }


    private String getMd5CheckSumForFileResource(FileResource fileResource) {
        return FetchCoreUtils.getFileMd5String(fileResource.getFile()) != null ? FetchCoreUtils.getFileMd5String(fileResource.getFile()) : "";
    }

    private void throwIfTerminated() throws Exception {
        if (isTerminated) {
            throw new Exception("FetchFileServer was already Shutdown. It cannot be restarted. Get a new Instance.");
        }
    }

    private void throwIfAddingReservedCatalogInfo(FileResource fileResource) {
        if (fileResource.getId() == FileRequest.CATALOG_ID
            || fileResource.getName() == FileRequest.CATALOG_NAME
            || fileResource.getFile() == FileRequest.CATALOG_FILE) {
            throw new IllegalArgumentException("File Resources 'id' cannot be: ${FileRequest.CATALOG_ID} " +
                "and 'name' cannot be: Catalog.json and " +
                "'file' cannot be: /Catalog.json");
        }
    }
}
